﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SimpleServiceBus.Bus.MessageManagement;
using SimpleServiceBus.Bus.MessageManagement.Serialization;
using SimpleServiceBus.Endpoint;

namespace SimpleServiceBus.Bus.Subscriptions
{
    public class XmlSubscriptionRepository : EndpointServiceBase, ISubscriptionRepository
    {
        private static string _subscriptionFileBaseDirectory;
        private readonly object _storageLock = new object();
        private System.Xml.Serialization.XmlSerializer _serializer;
        private Dictionary<string, Type> _knownTypes = new Dictionary<string, Type>();

        private readonly Dictionary<string, StoredSubscription> _subs = new Dictionary<string, StoredSubscription>();


        public Dictionary<string, Type> KnownTypes
        {
            get
            {
                return _knownTypes;
            }
            set
            {
                if (IsRunning)
                    throw new InvalidOperationException("KnownTypes must be set prior to starting this service.");
                _knownTypes = value;
            }
        }

        /// <summary>
        /// Sets an alternate base directory for storing subscriptions. By default,
        /// subscriptions will be stored in AppDomain.CurrentDomain.BaseDirectory in a file called Subscriptions.xml
        /// This property is not thread safe, and should only be set during application initialization.
        /// </summary>
        public static string SubscriptionFileBaseDirectory
        {
            get
            {
                if (String.IsNullOrEmpty(_subscriptionFileBaseDirectory) || !Directory.Exists(_subscriptionFileBaseDirectory))
                    _subscriptionFileBaseDirectory = AppDomain.CurrentDomain.BaseDirectory;
                return _subscriptionFileBaseDirectory;
            }
        }

        public static string SubscriptionFileFullPath
        {
            get { return Path.Combine(SubscriptionFileBaseDirectory, "Subscriptions.xml"); }
        }

        public IList<StoredSubscription> GetAllSubscriptions()
        {
            lock (_storageLock)
            {
                _subs.Clear();

                List<SerializableStoredSubscription> subscriptions = null;
                var loaded = new List<StoredSubscription>();


                if (File.Exists(SubscriptionFileFullPath))
                {
                    using (var fs = new FileStream(SubscriptionFileFullPath, FileMode.Open, FileAccess.Read))
                    {
                        subscriptions = (List<SerializableStoredSubscription>)_serializer.Deserialize(fs);
                        subscriptions.ForEach(
                            s =>
                            loaded.Add(new StoredSubscription { MessageType = _knownTypes[s.TypeName], Subscriber = s.Subscriber }));
                    }
                }

                if (loaded.Count > 0)
                {
                    foreach (StoredSubscription sub in loaded)
                    {
                        _subs.Add(sub.ToString(), sub);
                    }
                    return loaded;
                }
                return new List<StoredSubscription>();

            }
        }

        public void AddSubscription(Type messageType, string subscriber)
        {
            lock (_storageLock)
            {
                var sub = new StoredSubscription { MessageType = messageType, Subscriber = subscriber };
                if (!_subs.ContainsKey(sub.ToString()))
                {
                    _subs.Add(sub.ToString(), sub);
                    PersistSubscriptions();
                }
            }
        }

        public void RemoveSubscription(Type messageType, string subscriber)
        {
            lock (_storageLock)
            {
                var sub = new StoredSubscription { MessageType = messageType, Subscriber = subscriber };
                if (_subs.ContainsKey(sub.ToString()))
                {
                    _subs.Remove(sub.ToString());
                    PersistSubscriptions();
                }
            }
        }

        public void RemoveAllSubscriptionsForSubscriber(string subscriber)
        {
            lock (_storageLock)
            {
                StoredSubscription[] subs = _subs.Values.ToArray();
                foreach (StoredSubscription sub in subs)
                {
                    if (sub.Subscriber.Equals(subscriber, StringComparison.CurrentCultureIgnoreCase))
                        _subs.Remove(sub.ToString());
                }
                PersistSubscriptions();
            }
        }

        private void PersistSubscriptions()
        {
            try
            {
                var subs = new List<SerializableStoredSubscription>();
                foreach (StoredSubscription sub in _subs.Values)
                {
                    subs.Add(new SerializableStoredSubscription
                    {
                        Subscriber = sub.Subscriber,
                        TypeName = sub.MessageType.Namespace + "." + sub.MessageType.Name
                    });
                }

                using (var fs = new FileStream(SubscriptionFileFullPath, FileMode.Create, FileAccess.Write))
                {
                    _serializer.Serialize(fs, subs);
                    fs.Flush();
                }
            }
            catch (Exception)
            {
                if (File.Exists(SubscriptionFileFullPath))
                    File.Delete(SubscriptionFileFullPath);
                throw;
            }

        }

        protected override void StartService()
        {
            if (!Directory.Exists(SubscriptionFileBaseDirectory))
                Directory.CreateDirectory(SubscriptionFileBaseDirectory);

            var msgMgr = ServiceManager.GetService<IMessageTypeManagementService>();
            if (msgMgr != null)
            {
                foreach (Type messageType in msgMgr.GetRegisteredMessageTypes())
                    _knownTypes[messageType.Namespace + "." + messageType.Name] = messageType;
            }

            _serializer = new System.Xml.Serialization.XmlSerializer(
                typeof(object), new[] { typeof(SerializableStoredSubscription), typeof(List<SerializableStoredSubscription>) });

        }

        protected override void ShutDownService()
        {
            _serializer = null;
        }

        /// <summary>
        /// Incredibly, because of the TMessageType property of StoredSubscription,
        /// I cannot figure a way to succesfully serialize it using either
        /// XmlSerializer or DataContractSerializer. ARGH. Anyway, this will
        /// hopefully act as a disgusting, ugly jerry rigged alternative.
        /// </summary>
        [Serializable]
        public class SerializableStoredSubscription
        {
            public string TypeName;
            public string Subscriber;

        }
    }
}
